package com.wss.agent.maven.plugin.licenses;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;

import com.wss.agent.api.LicenseAnalysisRequest;
import com.wss.agent.api.LicenseAnalysisResult;
import com.wss.agent.maven.plugin.constants.Constants;
import com.wss.agent.maven.plugin.service.PluginService;
import com.wss.agent.maven.plugin.service.PluginServiceImpl;
import com.wss.agent.maven.plugin.utils.DependencyInfoBuilder;
import com.wss.agent.maven.plugin.validate.InputValidator;
import com.wss.agent.model.DependencyInfo;

/**
 * Sends information regarding the dependencies to White Source and receives the License analysis. 
 *
 * @goal licenses
 * @requiresProject true
 * @requiresOnline true
 * @aggregator false
 * 
 * @author tom.shapira
 */
public class LicenseAnalysisMojo extends AbstractMojo {

	/* --- Static members --- */
	
	private static final String LOG_SEPERATOR = "                                                   ";
	
	private static final int LICENSE_NAME_LENGTH = 40;
	
	/* --- Members --- */
	
	/**
	 * The Maven Project.
	 *
	 * @parameter expression="${project}" default-value="${project}"
	 * @required true
	 * @readonly true
	 */
	private MavenProject project = null;
	
	/**
	 * Set this to 'true' to bypass artifact deploy.
	 *       
	 * @parameter default-value="false"
	 */
	private boolean skip;
	
	/**
	 * Indicates whether the build will continue even if there are errors.
	 *
	 * @parameter default-value="false"
	 */
	private boolean failOnError;
	
	private PluginService service;
	
	/* --- Maven plugin implementation methods --- */
	
    public void execute() throws MojoExecutionException {
		if (skip) {
			getLog().info(Constants.SKIP_LICENSES);
		} else {
			try {
				doExecute();
			} catch (MojoExecutionException e) {
				handleError(e);
			}
		}
    }
    
    /* --- Private methods --- */
    
    /**
	 * Wrapper for the execute() method for easier error handling.
	 */
	private void doExecute() throws MojoExecutionException {
		validateInputs(project);
		
		// collect dependencies
		Set<DependencyInfo> dependencies = new HashSet<DependencyInfo>();
		processProject(project, dependencies);
		processChildren(project, dependencies);
		
		if (dependencies.isEmpty()) {
			getLog().info(Constants.INFO_NO_DEPENDENCIES);
		} else {
			LicenseAnalysisResult result = getLicenseAnalysis(dependencies);
			
			// show result in log
			logResult(result);
		}
	}

	/**
	 * The method perform the entire license analysis procedure.
	 * 
	 * @param dependencies The dependencies to analyze.
	 * 
	 * @return License analysis procedure result.
	 * 
	 * @throws MojoExecutionException In case of errors during the operation.
	 */
	private LicenseAnalysisResult getLicenseAnalysis(Collection<DependencyInfo> dependencies) throws MojoExecutionException {
		LicenseAnalysisRequest request = new LicenseAnalysisRequest(dependencies);

		service = PluginServiceImpl.getInstance();
		service.setLog(getLog());
		return service.getLicenseAnalysis(request);
	}

	/**
	 * Gets the project dependencies.
	 * 
	 * @param mavenProject Maven project.
	 */
	private void processProject(MavenProject project, Set<DependencyInfo> dependencies) {
		for (Dependency dependency : project.getDependencies()) {
			dependencies.add(
					DependencyInfoBuilder.fromDependency(dependency));
		}
	}
	
	/**
	 * The method process the collected projects of the given project. 
	 * 
	 * @param project Maven project.
	 */
	private void processChildren(MavenProject project, Set<DependencyInfo> dependencies) {
		List<MavenProject> collectedProjects = project.getCollectedProjects();
		if (collectedProjects != null) {
			for (MavenProject collectedProject : collectedProjects) {
				processProject(collectedProject, dependencies);
			}	
		}
	}
	
	/**
	 * Logs the operation results. 
	 * 
	 * @param result
	 */
	private void logResult(LicenseAnalysisResult result) {
		getLog().info("");

		Map<String, Integer> licenceDistribution = result.getLicenseDistribution();
		if (licenceDistribution.isEmpty()) {
			getLog().info(Constants.INFO_NO_RESULTS);
		} else {
			getLog().info(Constants.INFO_LICENSE_ANALYSIS);
			getLog().info("");
			for (Entry<String, Integer> entry : getSortedList(licenceDistribution)) {
				logLicenseResult(entry.getKey(), entry.getValue());
			}
			getLog().info("");
			getLog().info(Constants.INFO_CONTACT);
			getLog().info("");
		}
	}
	
	private List<Entry<String, Integer>> getSortedList(Map<String, Integer> licenceDistribution) {
		List<Entry<String, Integer>> sortedLicenses = new ArrayList<Entry<String,Integer>>();
		
		sortedLicenses.addAll(licenceDistribution.entrySet());
		
		// sort by license appearances
		Collections.sort(sortedLicenses, new Comparator<Entry<String,Integer>>() {
			public int compare(Entry<String,Integer> o1, Entry<String,Integer> o2) {
				return o1.getKey().compareTo(o2.getKey());
			};
		});
		return sortedLicenses;
	}

	/**
	 * Writes the license result to the log.
	 * 
	 * @param license Name of the license.
	 * @param appearances Number of libraries under license;
	 */
	private void logLicenseResult(String license, Integer appearances) {
		// shorten name if necessary
		if (license.length() > LICENSE_NAME_LENGTH) {
			license = license.substring(0, LICENSE_NAME_LENGTH) + "..";
		}
		
		StringBuilder sb = new StringBuilder(LOG_SEPERATOR);
		sb.replace(0, license.length(), license + " ");
		
		// align number to right
		String alignString = "  ";
		if (appearances > 9) {
			alignString = " ";
			if (appearances > 99) {
				alignString = "";
			}
		}
		sb.append(alignString + appearances);
		
		getLog().info(sb.toString());
	}
	
	/**
	 * Validates mandatory maven parameter inputs required for the request.
	 * 
	 * @throws MojoExecutionException In case of any invalid property.
	 */
	private void validateInputs(MavenProject project) throws MojoExecutionException {
		// check if project exists
		if (project == null) {
			throw new MojoExecutionException(Constants.ERROR_NO_PROJECT);
		}
		
		// check if plugin was run from folder without POM file
		if (InputValidator.isStandAlonePom(project)) {
			throw new MojoExecutionException(Constants.ERROR_NOT_PROJECT_FOLDER);
		}
	}
	
    /**
	 * Handle error according to failOnError property.
	 * 
	 * @param e Exception thrown
	 * 
	 * @throws MojoExecutionException In case failOnError is true
	 */
	private void handleError(Exception e) throws MojoExecutionException {
		String message = e.getMessage();
		
		if (failOnError) {
			throw new MojoExecutionException(message);
		} else {
			getLog().warn(message);
			getLog().debug(e);
		}
	}
}
